﻿#region File and License Information
/*
<File>
	<Copyright>Copyright © 2007, Daniel Vaughan. All rights reserved.</Copyright>
	<License see="//License.txt"/>
	<Owner Name="Daniel Vaughan" Email="danielvaughan@outcoder.com"/>
	<CreationDate>2009-03-12 12:01:40Z</CreationDate>
	<LastSubmissionDate>$Date: $</LastSubmissionDate>
	<Version>$Revision: $</Version>
</File>
*/
#endregion

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Xml;

namespace DanielVaughan.Logging.Configuration
{
	public class LogRepository
	{
		#region Singleton implementation

		LogRepository()
		{
		}

		public static LogRepository Instance
		{
			get
			{
				return Nested.instance;
			}
		}

		class Nested
		{
			// Explicit static constructor to tell C# compiler
			// not to mark type as beforefieldinit
			static Nested()
			{
			}

			internal static readonly LogRepository instance = new LogRepository();
		}

		#endregion
 
		readonly Dictionary<ILogStrategy, List<IFilter>> logStrategies = new Dictionary<ILogStrategy, List<IFilter>>();
		readonly Dictionary<string, ILogStrategy> logStrategyNames = new Dictionary<string, ILogStrategy>();
		string configurationErrorPrefix = "Configuration Error: "; /* TODO: Make localizable resource. */
		
		public IEnumerable<ILogStrategy> LogStrategies
		{
			get
			{
				return logStrategies.Keys;
			}
		}

		/// <summary>
		/// Gets the filters for the specified log strategy.
		/// </summary>
		/// <param name="logStrategy">The log strategy.</param>
		/// <returns>THe filters for the specified log strategy.</returns>
		public IEnumerable<IFilter> GetFilters(ILogStrategy logStrategy)
		{
			ArgumentValidator.AssertNotNull(logStrategy, "logStrategy");
			return logStrategies[logStrategy];
		}

		/// <summary>
		/// Gets the log strategy with the specified name.
		/// </summary>
		/// <param name="logStrategyName">Name of the log strategy.</param>
		/// <returns>The log strategy with the specified name.</returns>
		public ILogStrategy GetLogStrategy(string logStrategyName)
		{
			return logStrategyNames[logStrategyName];
		}

		/// <summary>
		/// Adds a log strategy to the list of log strategies.
		/// </summary>
		/// <param name="logStrategyName">Name of the log strategy.</param>
		/// <param name="logStrategy">The log strategy.</param>
		/// <param name="filters">The filters. Can be null.</param>
		public void AddLogStrategy(string logStrategyName, ILogStrategy logStrategy, IEnumerable<IFilter> filters)
		{
			ArgumentValidator.AssertNotNull(logStrategyName, "logStrategyName");
			ArgumentValidator.AssertNotNull(logStrategy, "logStrategy");
            
			if (logStrategies.ContainsKey(logStrategy))
			{
				List<IFilter> previousFilters;
				if (logStrategies.TryGetValue(logStrategy, out previousFilters))
				{
					previousFilters.AddRange(previousFilters.Union(filters));
				}
				else
				{
					logStrategies[logStrategy] = new List<IFilter>(filters);
				}
			}
			else
			{
				logStrategies.Add(logStrategy, filters != null ? filters.ToList() : new List<IFilter>());
				logStrategyNames.Add(logStrategyName, logStrategy);
			}

			InternalLog.Info(string.Format("Added log strategy to repository. Name: {0}, Type: {1}", /* TODO: Make localizable resource. */
				logStrategyName, logStrategy));
		}

		internal void Load(XmlElement rootElement)
		{
			ArgumentValidator.AssertNotNull(rootElement, "rootElement");

			StringBuilder errorBuilder = new StringBuilder();

			/* GetElementsByTagName ignores namespace, where as SelectNodes doesn't. */
			var logStrategiesElements = rootElement.GetElementsByTagName("LogStrategy");
			bool strategyExists = false;
			foreach (XmlElement logStrategyElement in logStrategiesElements)
			{
				strategyExists = true;
				var strategyNameAttribute = logStrategyElement.Attributes["Name"];
				if (strategyNameAttribute == null)
				{
					/* TODO: Make localizable resource. */
					WriteConfigurationError(errorBuilder, "Name attribute does not exist for strategy. Element: " 
						+ logStrategyElement, null);
					continue;
				}

				var strategyTypeElement = logStrategyElement.Attributes["Type"];
				if (strategyTypeElement == null)
				{
					/* TODO: Make localizable resource. */
					WriteConfigurationError(errorBuilder, "Type name attribute does not exist. Element: " 
						+ logStrategyElement, null);
					continue;
				}

				ILogStrategy logStrategy;
				try
				{
					Type type = Type.GetType(strategyTypeElement.Value);
					logStrategy = (ILogStrategy)Activator.CreateInstance(type);
				}
				catch (Exception ex)
				{
					/* TODO: Make localizable resource. */
					WriteConfigurationError(errorBuilder, "LogStrategy is missing or of wrong type. Name: " 
						+ strategyNameAttribute.Value, ex);
					continue;
				}

				var filters = new List<IFilter>();
				logStrategies.Add(logStrategy, filters);
				logStrategyNames.Add(strategyNameAttribute.Value, logStrategy);

				InternalLog.Info(string.Format("Added log strategy to repository. Name: {0}, Type: {1}", /* TODO: Make localizable resource. */
					strategyNameAttribute.Value, strategyTypeElement.Value));

				var filterElements = logStrategyElement.GetElementsByTagName("Filter");
				foreach (XmlElement filterElement in filterElements)
				{
					var filterNameAttribute = filterElement.Attributes["Name"];
					if (filterNameAttribute == null)
					{
						errorBuilder.Append(configurationErrorPrefix);
						string errorMessage = "Name attribute does not exist for filter. Name: " + strategyNameAttribute; /* TODO: Make localizable resource. */
						InternalLog.Error(errorMessage);
						errorBuilder.Append(errorMessage);
						continue;
					}

					var filterTypeAttribute = filterElement.Attributes["Type"];
					if (filterTypeAttribute == null)
					{
						/* TODO: Make localizable resource. */
						WriteConfigurationError(errorBuilder, string.Format("Type name for filter attribute does not exist. Strategy name: {0}, Filter name: {1}", 
							strategyNameAttribute, filterNameAttribute), null);
						continue;
					}

					IFilter filter;
					Type filterType;
					try
					{
						filterType = Type.GetType(filterTypeAttribute.Value);
						filter = (IFilter)Activator.CreateInstance(filterType);
					}
					catch (Exception ex)
					{
						/* TODO: Make localizable resource. */
						WriteConfigurationError(errorBuilder, string.Format("Filter is missing or of wrong type. Strategy name: {0}, Filter name: {1}",
							strategyNameAttribute, filterNameAttribute), ex);
						continue;
					}

					if (filter != null)
					{
						try
						{
							filter.Load(filterElement);
						}
						catch (Exception ex)
						{
							/* TODO: Make localizable resource. */
							WriteConfigurationError(errorBuilder, string.Format("Filter could not be loaded. Strategy name: {0}, Filter name: {1}, Filter type: {2}",
								strategyNameAttribute, filterNameAttribute, filterType), ex);
							continue;
						}
						
						filters.Add(filter);

						InternalLog.Info(string.Format("Added filter for strategy '{0}'. Name: {1}, Type: {2}", /* TODO: Make localizable resource. */
							strategyNameAttribute.Value, filterNameAttribute.Value, filterType));
					}
				}
			}

			if (!strategyExists)
			{
				InternalLog.Warn("No log strategies found in Clog config element.");
			}

			string errors = errorBuilder.ToString();
			if (errors.Length > 0)
			{
				throw new ClientLoggingException(errors);
			}
		}

		void WriteConfigurationError(StringBuilder errorBuilder, string message, Exception exception)
		{
			errorBuilder.Append(configurationErrorPrefix);
			InternalLog.Error(message, exception);
			errorBuilder.Append(message + "Exception: " +  exception);
		}
	}
}
